/* 
 * File:   main.cpp
 * Author: marduke
 *
 * Created on May 4, 2010, 8:24 PM
 */
#include <fstream>

#include "main.h"
/*
 * 
 */
// Declaracion de las variables globales
bool** actual;
bool** tmp;
bool** nueva;
pair<int, int> a;
int H;
int N;
int imax;
int jmax;
int Lapareamiento;
int Lsoledad;
int Lhacinamiento;
int Nsnapshot;
string PrefijoFoto;
string EstadoInicial;
vector< set<pair<int, int> > > *ActCel;
vector< set<pair<int, int> > > *NuevoCel;
pthread_barrier_t barr;
pthread_barrier_t barrMain;


void * codigoHilo(void *thread_id) {
    int *id_ptr, tarea_id, i;
    set< pair<int, int> > *candidatos = new set<pair<int, int> >;



    id_ptr = (int *) thread_id;

    tarea_id = *id_ptr;
    int iIni = id_ptr[0];
    int iFinal = id_ptr[1];
    int h = id_ptr[2];



    while (true) {

        candidatos = iniciarActCel(iIni, iFinal, h);
        //Recorro los candidatos
        corridaMatriz(candidatos);

        int rc = pthread_barrier_wait(&barr);
        if (rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD) {
            printf("Tumbe el muro.\n");
            exit(-1);
        }

        int rc1 = pthread_barrier_wait(&barrMain);
        if (rc1 != 0 && rc1 != PTHREAD_BARRIER_SERIAL_THREAD) {
            printf("Tumbe el muro.\n");
            exit(-1);
        }

  //      cout << "Encontre Todos los hilos" << endl;

        free(candidatos);

    }

    pthread_exit(NULL);

}

/*
 * Cuenta los vecinos dados una coordenada i,j.
 */


int contarVecinos(int il, int jl) {
    int vecinos = 0;
    for (int i = -1; i < 2; i++) {
        for (int j = -1; j < 2; j++) {
            if (i == 0 && j == 0)
                continue;
            if (actual[(il + i < 0 ? imax - 1 : il + i) % imax][(jl + j < 0 ? jmax - 1 : jl + j) % jmax])
                vecinos++;
        }
    }
    return vecinos;
}

/*
 * Se Verifica si en el punto i j de la matriz, la celula vive o muere
 */


int verificacion(int i, int j) {
    int vecinos = contarVecinos(i, j);
    // cout << "I: " << i << " j: " << j << " tiene " << vecinos << endl;
    if (actual[i][j]) {

        if (vecinos < Lsoledad || vecinos > Lhacinamiento)
            nueva[i][j] = false;
        else
            nueva[i][j] = true;



    } else {

        if (vecinos == Lapareamiento)
            nueva[i][j] = true;
    }
}

/*
 * Se recorre todo el conjunto de posibles elementos y se verifican.
 * Para ver si las celulas viven o mueren.
 */

int corridaMatriz(set<pair<int, int> > *posibles) {

    set<pair<int, int> >::iterator it;


    for (it = posibles->begin(); it != posibles->end(); it++) {
        verificacion(it->first, it->second);

    }
}

/*
 * Imprime la matriz en la pantalla.
 */

int snapshot() {
    for (int i = 0; i < imax; i++) {
        for (int j = 0; j < jmax; j++) {
            cout << actual[i][j] << " ";
        }
        cout << endl;
    }
    cout << endl;
}

/*
 *Carga el estado inicial de el archivo que se pasa como parametro en el main.
 */

int cargarEstadoInicial() {
    ifstream in;
    int i, j;

    in.open(EstadoInicial.c_str());
    while (!in.eof()) {
        in >> i >> j;

        if (i < imax && j < jmax)
            actual[i][j] = true;
    }
}

/*
 * Carga los datos en las variables globales.
 */


int cargarDatos(int argc, char** argv) {



    for (int i = 1; i < argc; i++) {
        int j = i + 1;

        if (!strcmp(argv[i], "-n"))
            N = atoi(argv[j]);

        else if (!strcmp(argv[i], "-w"))
            jmax = atoi(argv[j]);

        else if (!strcmp(argv[i], "-h"))
            imax = atoi(argv[j]);

        else if (!strcmp(argv[i], "-la"))
            Lapareamiento = atoi(argv[j]);

        else if (!strcmp(argv[i], "-ls"))
            Lsoledad = atoi(argv[j]);

        else if (!strcmp(argv[i], "-lh"))
            Lhacinamiento = atoi(argv[j]);

        else if (!strcmp(argv[i], "-o"))
            PrefijoFoto.assign(argv[j]);

        else if (!strcmp(argv[i], "-i"))
            EstadoInicial.assign(argv[j]);

        else if (!strcmp(argv[i], "-s"))
            Nsnapshot = atoi(argv[j]);

        else if (!strcmp(argv[i], "-m"))
            H = atoi(argv[j]);

        else
            cout << "Este parametro no es conocido" << argv[i] << ";" << endl;

        i = j;

    }

    //Reserva de espacio para las matrices
    

    if ((actual = (bool**) calloc(imax, sizeof(bool*)) ) == NULL || (nueva = (bool**) calloc(imax, sizeof (bool*))) == NULL) {
        cout << "Error en la reserva de memoria" << endl;
        exit(-1);
    }
    for (int i = 0; i < imax; i++) {

        if ((actual[i] = (bool*) calloc(jmax, sizeof (bool))) == NULL || ((nueva[i] = (bool*) calloc(jmax, sizeof (bool))) == NULL)) {
            cout << "Error en la reserva de memoria" << endl;
            exit(-1);
        }

    }

}

/**
 * Inicia el vector de actividad celular
 *
 **/
set< pair<int, int > > *iniciarActCel(int i1, int i2, int h) {
    set<pair<int, int> > *retorno = new set<pair<int, int> >;

    if (i1 >= imax) {
        cout << "Error al segmentar matriz" << endl;
        return NULL;
    }

    for (int i = i1; i <= i2; i++) {
        for (int j = 0; j < jmax; j++) {

            if (actual[i][j]) {
                retorno->insert(make_pair(i, j));
                retorno->insert(make_pair(i, (j + 1) % jmax));
                retorno->insert(make_pair(i, (j - 1 < 0 ? jmax - 1 : j - 1)));

                if (i - 1 >= i1) {
                    retorno->insert(make_pair(i - 1, j));
                    retorno->insert(make_pair(i - 1, (j + 1) % jmax));
                    retorno->insert(make_pair(i - 1, (j - 1 < 0 ? jmax - 1 : j - 1)));
                }

                if (i + 1 <= i2) {
                    retorno->insert(make_pair(i + 1, j));
                    retorno->insert(make_pair(i + 1, (j + 1) % jmax));
                    retorno->insert(make_pair(i + 1, (j - 1 < 0 ? jmax - 1 : j - 1)));
                }

            }

        }
    }

    for (int j = 0; j < jmax; j++) {

        if (actual[(i1 - 1 < 0 ? imax - 1 : i1 - 1)][j]) {

            retorno->insert(make_pair(i1, j));
            retorno->insert(make_pair(i1, (j + 1) % jmax));
            retorno->insert(make_pair(i1, (j - 1 < 0 ? jmax - 1 : j - 1)));
        }


        if (actual[(i2 + 1) % imax][j]) {
   
            retorno->insert(make_pair(i2, j));
            retorno->insert(make_pair(i2, (j + 1) % jmax));
            retorno->insert(make_pair(i2, (j - 1 < 0 ? jmax - 1 : j - 1)));
        }

    }

    return retorno;

}

void crearSubCon() {
    ActCel = new vector< set< pair<int, int> > >;
    NuevoCel = new vector< set< pair<int, int> > >;
    vector<set<pair<int, int> > >::iterator it, itN;
    for (int i = 0; i < H; i++) {
        it = ActCel->begin();
        itN = NuevoCel->begin();
        ActCel->insert(it, set<pair<int, int> >());
        NuevoCel->insert(itN, set<pair<int, int> >());
    }
}

int main(int argc, char** argv) {
    // Declaracion de las variables locales
    int rc,rc1, n, it;
    n = 1;
    it = 1;
    // Leo los argumentos de la entrada


    if (argc != 21) {
        cout << "No tienes los argumentos correctos" << endl;
        exit(-1);
    }
    cargarDatos(argc, argv);
    cargarEstadoInicial();
    crearSubCon();
    // Barrier initialization
    if (pthread_barrier_init(&barr, NULL, H + 1)) {
        printf("No ahi bloques para construir el muro\n");
        return -1;
    }

    // Barrier initialization
    if (pthread_barrier_init(&barrMain, NULL, H + 1)) {
        printf("No ahi bloques para construir el muro\n");
        return -1;
    }
    //Creación de hilos


    pthread_t threads[H];
    pthread_attr_t mis_atr;
    int tarea_ids[H][3];
    int t;

    if (pthread_attr_init(&mis_atr)) {
        perror("No se pudo inicializar atributos");
        exit(1);
    }

    pthread_attr_setscope(&mis_atr, PTHREAD_SCOPE_PROCESS);

    int tmpI = imax;
    int h = H;
    int valor = 0;

    for (t = 0; t < H; t++) {

        int tam = tmpI / h;
        h--;
        tmpI = tmpI - tam;
        valor += tam;

        tarea_ids[t][0] = valor - tam;
        tarea_ids[t][1] = valor - 1;
        tarea_ids[t][2] = t;
        pthread_create(&threads[t], NULL, codigoHilo, (void *) tarea_ids[t]);

    }

    for (int j = 0; j < N; j++) {


        //Aqui tiene que esperar
        // Synchronization point
        rc = pthread_barrier_wait(&barr);
        if (rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD) {
            printf("Tumbe el muro.\n");
            exit(-1);
        }



        //Cambia la matriz y crea de nuevo la nueva
        
        tmp = nueva;
        if ((nueva = (bool**) calloc(imax, sizeof (bool*))) == NULL) {
            cout << "Error en la reserva de memoria" << endl;
            exit(-1);
        }
        for (int i = 0; i < imax; i++) {

            if (((nueva[i] = (bool*) calloc(jmax, sizeof (bool))) == NULL)) {
                cout << "Error en la reserva de memoria" << endl;
                exit(-1);
            }

            free(actual[i]);

        }
        free(actual);
        actual = tmp;
        //snapshot();

        if(n == Nsnapshot){
            char  buffer [50];
            string buf= buffer;
            sprintf(buffer,"%d",it);
            char * nombre  ;
            nombre = (char *)(PrefijoFoto+buf).c_str() ;
            ofstream salida( nombre  , ios::out);
            for(int i = 0 ; i < imax ; i++){
                for (int j = 0 ; j < jmax ; j++){
                    if(actual[i][j])
                    salida << i << " " << j <<endl ;
                }
            }
            n = 0;
            
        }
        it++;
        n++;

        rc1 = pthread_barrier_wait(&barrMain);
        if (rc1 != 0 && rc1 != PTHREAD_BARRIER_SERIAL_THREAD) {
            printf("Tumbe el muro.\n");
            exit(-1);
        }


    }

    for (int i = 0; i < H; i++) {
        pthread_cancel(threads[i]);

    }



    return (EXIT_SUCCESS);
}